from inspect import getargspec

class Event(dict):
    def __init__(self,d):
        dict.__init__(self,d)
        self['the_event'] = self
        self.terminate = False
        #self['args'] = [] # TODO: check

class RuleSet(object):
    def __init__(self):
        self.rules = {}
    
    # using '_self' to avoid name collisions with **e, probably a better way to do this
    def raiseEvent(_self, event_name, **e):#*args, **e): # TODO: check
        e = Event(e) # just to set e['the_event'] = e
        #e['args'] = args
        if not event_name in _self.rules: return # TODO: throw a warning?
        for rule in _self.rules[event_name]:
            rule(e)
            if e.terminate: break

    def eventMaker(self,func,name=''):
        if name=='':
            name = func.__name__
            #if name in self.rules: raise KeyError('Event named '+name+' already exists')
        self.rules[name] = []
        argnames = getargspec(func)[0]
        def _invokeRules(*args,**kwargs):#(*args,**kwargs): # TODO: check
            kwargs.update(dict(zip(argnames,args)))
            self.raiseEvent(name,**kwargs)#*args,**kwargs) # TODO: check
        self.rule(name)(func) # this adds the given function exactly as a rule
        return _invokeRules

    def rule(self,eventname):
        def _ruleMaker(func):
            argnames = getargspec(func)[0]
            def _callRule(event):
                subset = dict((x,event[x]) if x in event else (x,None) for x in argnames)
                retval = func(**subset)#func(*event.args,**subset) # TODO: check
                if retval: event.update({func.__name__+'_return':retval}) # any return values get added to the event dict

            if not eventname in self.rules: self.rules[eventname] = [_callRule]
            else: self.rules[eventname].append(_callRule)
        return _ruleMaker


class Commandable(object): #TODO: should this be derived from Entity?
    def __init__(self,commands):
        self.has_command = dict((c,False) for c in commands)
        self.command_rules = RuleSet()

    def giveCommand(self, command, value=True, **kwargs):
        self.has_command[command] = value
        self.command_rules.raiseEvent('give'+command, self=self, value=value,**kwargs)

    def updateCommands(self):
        for command,val in self.has_command.items():
            if val: self.command_rules.raiseEvent('on'+command, self=self)
        self.command_rules.raiseEvent('default',self=self) # TODO: needed?


class FSM(RuleSet):
    '''A simple finite state machine implemented via the rules system.'''

    def __init__(self):
        self.states = {}
        self.rules = {} # this is the current state, the name is just to work with RuleSet

    def addState(self,name):
        self.states[name] = {}
        self.rules = self.states[name]

    def selectState(self,name):
        '''Set the current state to that given by "name".'''
        self.rules = self.states[name]
        
    def ruleForState(self,state_name,rule_name):
        s = self.rules
        self.selectState(state_name)
        f = self.rule(rule_name)
        self.rules = s
        return f

if __name__ == '__main__':

    class Rungun(Commandable):
        def __init__(self):
            Commandable.__init__(self,['left','right','fire','run','walk'])
            self.direction = 0
            self.speed = 0
            self.maxspeed = 10
            self.jumping = False
            self.ammo = 3

            @self.command_rules.rule('giveleft')
            def giveLeft(self,value):
                if value: self.direction=-1
                else: self.direction=0
            @self.command_rules.rule('giveright')
            def giveRight(self,value):
                if value: self.direction=1
                else: self.direction=0
            @self.command_rules.rule('givefire')
            def giveFire(self):
                if self.ammo<=0:
                    print 'out of ammo'
                    self.has_command['fire'] = False
            @self.command_rules.rule('onrun')
            def onLeft(self):
                if self.speed < self.maxspeed: self.speed += 2
                print 'going left at:', self.speed
            @self.command_rules.rule('onwalk')
            def onRight(self):
                if self.speed < self.maxspeed/3: self.speed += 1
                print 'going right at:', self.speed
            @self.command_rules.rule('onfire')
            def onFire(self):
                self.ammo -= 1
                if self.ammo <=0: self.has_command['fire']=False
                print 'firing!!!'
            @self.command_rules.rule('default')
            def default(self):
                print 'I am updating!'

    m = Rungun()
    m.giveCommand('left',True)
    m.giveCommand('walk',True)
    m.giveCommand('fire',True)
    m.updateCommands()
    m.updateCommands()
    m.updateCommands()
    m.updateCommands()
    m.giveCommand('fire',True)
    m.updateCommands()


    
    rs = RuleSet()

    print 'testing rules...'

    @rs.rule('testevent')
    def testrule1(message,the_event):
        print 'testrule1 message:',message
        the_event['somevar'] = 42
        return 'retval'

    @rs.rule('testevent')
    def testrule2(something,somevar,testrule1_return):
        print 'testrule2 something:',something
        if somevar==42: print 'Hooray!! 42!!'
        print 'rule1 returned:',testrule1_return

    rs.raiseEvent('testevent',message='hello',something='Fred')

    @rs.eventMaker
    def jump(jumper,height):
        print jumper,'jumps up',height,'meters'

    @rs.rule('jump')
    def jumprule(height,the_event):
        if height>2:
            print the_event['jumper'],'collides with ceiling'

    jump(jumper='Fred',height=3)

    class thing:
        def __init__(self,name):
            self.name = name
        @rs.eventMaker
        def testmember(self):
            print 'hello, I am',self.name

    @rs.rule('testmember')
    def memberrule(self):
        print 'hello',self.name

    something = thing('Greg')
    something.testmember()
